#pragma once

#include <unordered_map>

#include "vmap/bih.h"

namespace vmap
{
    class ModelInstance;
    class GroupModel;
    class VMapManager;

    /**
     * @brief
     *
     */
    struct LocationInfo
    {
        /**
         * @brief
         *
         */
        LocationInfo() : hitInstance(0), hitModel(0), ground_Z(-G3D::inf()) {};
        const ModelInstance* hitInstance; /**< TODO */
        const GroupModel* hitModel; /**< TODO */
        float ground_Z; /**< TODO */
    };

    /**
     * @brief
     *
     */
    class StaticMapTree
    {
        /**
         * @brief
         *
         */
        typedef std::unordered_map<unsigned int, bool> loadedTileMap;
        /**
         * @brief
         *
         */
        typedef std::unordered_map<unsigned int, unsigned int> loadedSpawnMap;
    private:
        unsigned int iMapID; /**< TODO */
        bool iIsTiled; /**< TODO */
        BIH iTree; /**< TODO */
        ModelInstance* iTreeValues; // the tree entries /**< TODO */
        unsigned int iNTreeValues; /**< TODO */

        // Store all the map tile idents that are loaded for that map
        // some maps are not splitted into tiles and we have to make sure, not removing the map before all tiles are removed
        // empty tiles have no tile file, hence map with bool instead of just a set (consistency check)
        loadedTileMap iLoadedTiles; /**< TODO */
        // stores <tree_index, reference_count> to invalidate tree values, unload map, and to be able to report errors
        loadedSpawnMap iLoadedSpawns; /**< TODO */
        std::string iBasePath; /**< TODO */

    private:
        /**
         * @brief
         *
         * @param pRay
         * @param pMaxDist
         * @param pStopAtFirstHit
         * @return bool
         */
        bool getIntersectionTime(const G3D::Ray& pRay, float& pMaxDist, bool pStopAtFirstHit) const;
        // bool containsLoadedMapTile(unsigned int pTileIdent) const { return(iLoadedMapTiles.containsKey(pTileIdent)); }
    public:
        /**
         * @brief
         *
         * @param mapID
         * @param tileX
         * @param tileY
         * @return std::string
         */
        static std::string getTileFileName(unsigned int mapID, unsigned int tileX, unsigned int tileY);
        /**
         * @brief
         *
         * @param tileX
         * @param tileY
         * @return unsigned int
         */
        static unsigned int packTileID(unsigned int tileX, unsigned int tileY) { return tileX << 16 | tileY; }
        /**
         * @brief
         *
         * @param ID
         * @param tileX
         * @param tileY
         */
        static void unpackTileID(unsigned int ID, unsigned int& tileX, unsigned int& tileY) { tileX = ID >> 16; tileY = ID & 0xFF; }
        /**
         * @brief
         *
         * @param basePath
         * @param mapID
         * @param tileX
         * @param tileY
         * @return bool
         */
        static bool CanLoadMap(const std::string& basePath, unsigned int mapID, unsigned int tileX, unsigned int tileY);

        /**
         * @brief
         *
         * @param mapID
         * @param basePath
         */
        StaticMapTree(unsigned int mapID, const std::string& basePath);
        /**
         * @brief
         *
         */
        ~StaticMapTree();

        /**
         * @brief
         *
         * @param pos1
         * @param pos2
         * @return bool
         */
        bool isInLineOfSight(const G3D::Vector3& pos1, const G3D::Vector3& pos2) const;
        /**
         * @brief
         *
         * @param pos1
         * @param pos2
         * @param pResultHitPos
         * @param pModifyDist
         * @return bool
         */
        bool getObjectHitPos(const G3D::Vector3& pos1, const G3D::Vector3& pos2, G3D::Vector3& pResultHitPos, float pModifyDist) const;
        /**
         * @brief
         *
         * @param pPos
         * @param maxSearchDist
         * @return float
         */
        float getHeight(const G3D::Vector3& pPos, float maxSearchDist) const;
        /**
         * @brief
         *
         * @param pos
         * @param flags
         * @param adtId
         * @param rootId
         * @param groupId
         * @return bool
         */
        bool getAreaInfo(G3D::Vector3& pos, unsigned int& flags, int& adtId, int& rootId, int& groupId) const;
        /**
         * @brief
         *
         * @param pos
         * @param info
         * @return bool
         */
        bool GetLocationInfo(const Vector3& pos, LocationInfo& info) const;

        /**
         * @brief
         *
         * @param fname
         * @param vm
         * @return bool
         */
        bool InitMap(const std::string& fname, VMapManager* vm);
        /**
         * @brief
         *
         * @param vm
         */
        void UnloadMap(VMapManager* vm);
        /**
         * @brief
         *
         * @param tileX
         * @param tileY
         * @param vm
         * @return bool
         */
        bool LoadMapTile(unsigned int tileX, unsigned int tileY, VMapManager* vm);
        /**
         * @brief
         *
         * @param tileX
         * @param tileY
         * @param vm
         */
        void UnloadMapTile(unsigned int tileX, unsigned int tileY, VMapManager* vm);
        /**
         * @brief
         *
         * @return bool
         */
        bool isTiled() const { return iIsTiled; }
        /**
         * @brief
         *
         * @return unsigned int
         */
        unsigned int numLoadedTiles() const { return iLoadedTiles.size(); }

#ifdef MMAP_GENERATOR
    public:
        void getModelInstances(ModelInstance*& models, unsigned int& count);
#endif
    };

    /**
     * @brief
     *
     */
    struct AreaInfo
    {
        /**
         * @brief
         *
         */
        AreaInfo() : result(false), ground_Z(-G3D::inf()) {};
        bool result; /**< TODO */
        float ground_Z; /**< TODO */
        unsigned int flags; /**< TODO */
        int adtId; /**< TODO */
        int rootId; /**< TODO */
        int groupId; /**< TODO */
    };
}                                                           // VMAP


